5.5 嵌入类型

Go语言允许用户扩展或者修改已有类型的行为。这个功能对代码复用很重要,在修改已有类型以符合新类型的时候也很重要。这个功能是通过 嵌入类型 (type embedding)完成的。嵌入类型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的外部类型的内部类型。

通过嵌入类型,与内部类型相关的标识符会提升到外部类型上。这些被提升的标识符就像直接声明在外部类型里的标识符一样,也是外部类型的一部分。这样外部类型就组合了内部类型包含的所有属性,并且可以添加新的字段和方法。外部类型也可以通过声明与内部类型标识符同名的标识符来覆盖内部标识符的字段或者方法。这就是扩展或者修改已有类型的方法。

让我们通过一个示例程序来演示嵌入类型的基本用法,如代码清单5-50所示。

代码清单5-50 listing50.go

01 // 这个示例程序展示如何将一个类型嵌入另一个类型,以及
02 // 内部类型和外部类型之间的关系
03 package main
04
05 import (
06   "fmt"
07 )
08
09 // user在程序里定义一个用户类型
10 type user struct {
11   name string
12   email string
13 }
14
15 // notify实现了一个可以通过user类型值的指针
16 // 调用的方法
17 func (u *user) notify() {
18   fmt.Printf("Sending user email to %s<%s>\n",
19   u.name,
20   u.email)
21 }
22
23 // admin代表一个拥有权限的管理员用户
24 type admin struct {
25   user // 嵌入类型
26   level string
27 }
28
29 // main是应用程序的入口
30 func main() {
31   // 创建一个admin用户
32   ad := admin{
33     user: user{
34       name: "john smith",
35       email: "john@yahoo.com",
36     },
37     level: "super",
38   }
39
40   // 我们可以直接访问内部类型的方法
41   ad.user.notify()
42
43   // 内部类型的方法也被提升到外部类型
44   ad.notify()
45 }

在代码清单5-50中,我们的程序演示了如何嵌入一个类型,并访问嵌入类型的标识符。我们从第10行和第24行中的两个结构类型的声明开始,如代码清单5-51所示。

代码清单5-51 listing50.go:第09行到第13行,第23行到第27行

09 // user在程序里定义一个用户类型
10 type user struct {
11   name string
12   email string
13 }
23 // admin代表一个拥有权限的管理员用户
24 type admin struct {
25   user // 嵌入类型
26   level string
27 }

在代码清单5-51的第10行,我们声明了一个名为 user 的结构类型。在第24行,我们声明了另一个名为 admin 的结构类型。在声明 admin 类型的第25行,我们将 user 类型嵌入 admin 类型里。要嵌入一个类型,只需要声明这个类型的名字就可以了。在第26行,我们声明了一个名为 level 的字段。注意声明字段和嵌入类型在语法上的不同。

一旦我们将 user 类型嵌入 admin ,我们就可以说 user 是外部类型 admin 的内部类型。有了内部类型和外部类型这两个概念,就能更容易地理解这两种类型之间的关系。

代码清单5-52展示了使用 user 类型的指针接收者声明名为 notify 的方法。这个方法只是显示一行友好的信息,表示将邮件发给了特定的用户以及邮件地址。

代码清单5-52 listing50.go:第15行到第21行

15 // notify实现了一个可以通过user类型值的指针
16 // 调用的方法
17 func (u *user) notify() {
18   fmt.Printf("Sending user email to %s<%s>\n",
19   u.name,
20   u.email)
21 }

现在,让我们来看一下 main 函数,如代码清单5-53所示。

代码清单5-53 listing50.go:第30行到第45行

30 func main() {
31   // 创建一个admin用户
32   ad := admin{
33     user: user{
34       name: "john smith",
35       email: "john@yahoo.com",
36     },
37     level: "super",
38   }
39
40   // 我们可以直接访问内部类型的方法
41   ad.user.notify()
42
43   // 内部类型的方法也被提升到外部类型
44   ad.notify()
45 }

代码清单5-53中的 main 函数展示了嵌入类型背后的机制。在第32行,创建了一个 admin 类型的值。内部类型的初始化是用结构字面量完成的。通过内部类型的名字可以访问内部类型,如代码清单5-54所示。对外部类型来说,内部类型总是存在的。这就意味着,虽然没有指定内部类型对应的字段名,还是可以使用内部类型的类型名,来访问到内部类型的值。

代码清单5-54 listing50.go:第40行到第41行

40   // 我们可以直接访问内部类型的方法
41   ad.user.notify()

在代码清单5-54中第41行,可以看到对 notify 方法的调用。这个调用是通过直接访问内部类型 user 来完成的。这展示了内部类型是如何存在于外部类型内,并且总是可访问的。不过,借助内部类型提升, notify 方法也可以直接通过 ad 变量来访问,如代码清单5-55所示。

代码清单5-55 listing50.go:第43行到第45行

43   // 内部类型的方法也被提升到外部类型
44   ad.notify()
45 }

代码清单5-55的第44行中展示了直接通过外部类型的变量来调用 notify 方法。由于内部类型的标识符提升到了外部类型,我们可以直接通过外部类型的值来访问内部类型的标识符。让我们修改一下这个例子,加入一个接口,如代码清单5-56所示。

代码清单5-56 listing56.go

01 // 这个示例程序展示如何将嵌入类型应用于接口
02 package main
03
04 import (
05   "fmt"
06 )
07
08 // notifier是一个定义了
09 // 通知类行为的接口
10 type notifier interface {
11   notify()
12 }
13
14 // user在程序里定义一个用户类型
15 type user struct {
16   name string
17   email string
18 }
19
20 // 通过user类型值的指针
21 // 调用的方法
22 func (u *user) notify() {
23   fmt.Printf("Sending user email to %s<%s>\n",
24   u.name,
25   u.email)
26 }
27
28 // admin代表一个拥有权限的管理员用户
29 type admin struct {
30   user
31   level string
32 }
33
34 // main是应用程序的入口
35 func main() {
36   // 创建一个admin用户
37   ad := admin{
38     user: user{
39       name: "john smith",
40       email: "john@yahoo.com",
41     },
42     level: "super",
43   }
44
45   // 给admin用户发送一个通知
46   // 用于实现接口的内部类型的方法,被提升到
47   // 外部类型
48   sendNotification(&ad)
49 }
50
51 // sendNotification接受一个实现了notifier接口的值
52 // 并发送通知
53 func sendNotification(n notifier) {
54   n.notify()
55 }

代码清单5-56所示的示例程序的大部分和之前的程序相同,只有一些小变化,如代码清单5-57所示。

代码清单5-57 第08行到第12行,第51行到第55行

08 // notifier是一个定义了
09 // 通知类行为的接口
10 type notifier interface {
11   notify()
12 }
51 // sendNotification接受一个实现了notifier接口的值
52 // 并发送通知
53 func sendNotification(n notifier) {
54   n.notify()
55 }

在代码清单5-57的第08行,声明了一个 notifier 接口。之后在第53行,有一个 sendNotification 函数,接受 notifier 类型的接口的值。从代码可以知道, user 类型之前声明了名为 notify``的 方法,该方法使用指针接收者实现了 notifier 接口。之后,让我们看一下 main 函数的改动,如代码清单5-58所示。

代码清单5-58 listing56.go:第35行到第49行

35 func main() {
36   // 创建一个admin用户
37   ad := admin{
38     user: user{
39       name: "john smith",
40       email: "john@yahoo.com",
41     },
42     level: "super",
43   }
44
45   // 给admin用户发送一个通知
46   // 用于实现接口的内部类型的方法,被提升到
47   // 外部类型
48   sendNotification(&ad)
49 }

这里才是事情变得有趣的地方。在代码清单5-58的第37行,我们创建了一个名为 ad 的变量,其类型是外部类型 admin 。这个类型内部嵌入了 user 类型。之后第48行,我们将这个外部类型变量的地址传给 sendNotification 函数。编译器认为这个指针实现了 notifier 接口,并接受了这个值的传递。不过如果看一下整个示例程序,就会发现 admin 类型并没有实现这个接口。

由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。运行这个示例程序,会得到代码清单5-59所示的输出。

代码清单5-59 listing56.go的输出

20 // 通过user类型值的指针
21 // 调用的方法
22 func (u *user) notify() {
23   fmt.Printf("Sending user email to %s<%s>\n",
24   u.name,
25   u.email)
26 }
Output:
Sending user email to john smith<john@yahoo.com>

可以在代码清单5-59中看到内部类型的实现被调用。

如果外部类型并不需要使用内部类型的实现,而想使用自己的一套实现,该怎么办?让我们看另一个示例程序是如何解决这个问题的,如代码清单5-60所示。

代码清单5-60 listing60.go

01 // 这个示例程序展示当内部类型和外部类型要
02 // 实现同一个接口时的做法
03 package main
04
05 import (
06   "fmt"
07 )
08
08 // notifier是一个定义了
09 // 通知类行为的接口
11 type notifier interface {
12   notify()
13 }
14
15 // user在程序里定义一个用户类型
16 type user struct {
17   name string
18   email string
19 }
20
21 // 通过user类型值的指针
22 // 调用的方法
23 func (u *user) notify() {
24   fmt.Printf("Sending user email to %s<%s>\n",
25     u.name,
26     u.email)
27 }
28
29 // admin代表一个拥有权限的管理员用户
30 type admin struct {
31   user
32   level string
33 }
34
35 // 通过admin类型值的指针
36 // 调用的方法
37 func (a *admin) notify() {
38   fmt.Printf("Sending admin email to %s<%s>\n",
39     a.name,
40     a.email)
41 }
42
43 // main是应用程序的入口
44 func main() {
45   // 创建一个admin用户
46   ad := admin{
47     user: user{
48       name: "john smith",
49       email: "john@yahoo.com",
50     },
51     level: "super",
52   }
53
54   // 给admin用户发送一个通知
55   // 接口的嵌入的内部类型实现并没有提升到
56   // 外部类型
57   sendNotification(&ad)
58
59   // 我们可以直接访问内部类型的方法
60   ad.user.notify()
61
62   // 内部类型的方法没有被提升
63   ad.notify()
64 }
65
66 // sendNotification接受一个实现了notifier接口的值
67 // 并发送通知
68 func sendNotification(n notifier) {
69   n.notify()
70 }

代码清单5-60所示的示例程序的大部分和之前的程序相同,只有一些小变化,如代码清单5-61所示。

代码清单5-61 listing60.go:第35行到第41行

35 // 通过admin类型值的指针
36 // 调用的方法
37 func (a *admin) notify() {
38   fmt.Printf("Sending admin email to %s<%s>\n",
39     a.name,
40     a.email)
41 }

这个示例程序为 admin 类型增加了 notifier 接口的实现。当 admin 类型的实现被调用时,会显示 "Sending admin email" 。作为对比, user 类型的实现被调用时,会显示 "Sending user email"

main 函数里也有一些变化,如代码清单5-62所示。

代码清单5-62 listing60.go:第43行到第64行

43 // main是应用程序的入口
44 func main() {
45   // 创建一个admin用户
46   ad := admin{
47     user: user{
48       name: "john smith",
49       email: "john@yahoo.com",
50     },
51     level: "super",
52   }
53
54   // 给admin用户发送一个通知
55   // 接口的嵌入的内部类型实现并没有提升到
56   // 外部类型
57   sendNotification(&ad)
58
59   // 我们可以直接访问内部类型的方法
60   ad.user.notify()
61
62   // 内部类型的方法没有被提升
63   ad.notify()
64 }

代码清单5-62的第46行,我们再次创建了外部类型的变量 ad 。在第57行,将 ad 变量的地址传给 sendNotification 函数,这个指针实现了接口所需要的方法集。在第60行,代码直接访问 user 内部类型,并调用 notify 方法。最后,在第63行,使用外部类型变量 ad 来调用 notify 方法。当查看这个示例程序的输出(如代码清单5-63所示)时,就会看到区别。

代码清单5-63 listing60.go的输出

Sending admin email to john smith<john@yahoo.com>
Sending user email to john smith<john@yahoo.com>
Sending admin email to john smith<john@yahoo.com>

这次我们看到了 admin 类型是如何实现 notifier 接口的,以及如何由 sendNotification 函数以及直接使用外部类型的变量 ad 来执行 admin 类型实现的方法。这表明,如果外部类型实现了 notify 方法,内部类型的实现就不会被提升。不过内部类型的值一直存在,因此还可以通过直接访问内部类型的值,来调用没有被提升的内部类型实现的方法。

results matching ""

    No results matching ""